package ex1;


/**
 * The Worker Class (extends Thread) each instance of this class is a worker
 * that works in parallel on his part of the board.
 */
public class Worker extends Thread {

	public class Lock {
		// used to synchronize between workers

		public synchronized void doNotify() {
			notifyAll();
		}
	}

	private Cell[][] matrix; // the matrix where we do the work
	boolean[][] outputField; // the output field
	Worker[][] workersMatrix; // matrix of the workers (threads), needed to
	// inform them about changes
	private int xIndex; // x index of the worker in workers_matrix
	private int yIndex; // y index of the worker in workers_matrix
	private int firstCellX; // x index of the first cell in the worker's split
	private int firstCellY; // y index of the first cell in the worker's split
	private int height; // height of the worker's split (through x coordinate)
	private int width; // width of the worker's split (through y coordinate)
	private Boolean changesInOthers; // turned on each time other workers
	// informs me about changes
	// turned off when i start a new iteration (passage over the matrix)
	private boolean changesInMyWork; // turned on each time i make a change in
	// my matrix
	// turned off when i start a new iteration (passage over the matrix)
	private int finished; // counting number of cell that reached the wanted
	// generation
	private Lock lock; // lock used to synchronize the read/write from

	// 'others_work'

	public Worker(Cell[][] matrix, boolean[][] outputField,
			Worker[][] workersMatrix, int firstCellX, int firstCellY, int x,
			int y, int height, int width) {
		this.matrix = matrix;
		this.workersMatrix = workersMatrix;
		this.xIndex = x;
		this.yIndex = y;
		this.firstCellX = firstCellX;
		this.firstCellY = firstCellY;
		this.height = height;
		this.width = width;
		this.finished = 0;
		this.changesInOthers = new Boolean(false);
		this.changesInMyWork = true;
		this.lock = new Lock();
		this.outputField = outputField;
	}
	

	/**
	 * the method sets 'borders characteristics' to the cells that are in
	 * contact with cells that belongs to other workers
	 */

	public void setBorders() {
		
		boolean up = false, down = false, left = false, right = false;
		
		
		if (firstCellX > 0)
			up = true;
		if (firstCellX + height - 1 < matrix.length - 1)
			down = true;

		for (int j = firstCellY; j < firstCellY + width; j++) {
			matrix[firstCellX][j].setBorder(0, up);
			matrix[firstCellX + height - 1][j].setBorder(1, down);
		}

		if (firstCellY > 0)
			left = true;
		if (firstCellY + width - 1 < matrix[0].length - 1)
			right = true;

		for (int i = firstCellX; i < firstCellX + height; i++) {
			matrix[i][firstCellY].setBorder(2, left);
			matrix[i][firstCellY + width - 1].setBorder(3, right);
		}
	}

	/**
	 * method invoked when the thread is runned.
	 */
	@Override
	public void run() {

		updateAllNeighbours();
		
		int size = width * height;

		while (finished < size) { // while not all cells finished
			if (!(changesInMyWork)) {
				synchronized (lock) {
					while (!(changesInOthers.booleanValue())) {
						try {
							lock.wait();
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
					changesInOthers = new Boolean(false);
				}
			}

			changesInMyWork = false;

			// advancing iteration
			advance();
		}
	}


	private void advance() {
		for (int i = firstCellX; i < firstCellX + height; i++) {
			for (int j = firstCellY; j < firstCellY + width; j++) {
				if (matrix[i][j].isDone() || !(matrix[i][j].isReady()))
					continue;
				matrix[i][j].nextGeneration();
				if (matrix[i][j].isDone()) {
					finished++;
					outputField[i][j] = matrix[i][j].isAlive();
				}
				updateNeighbours(i, j); // tell
				// neighbors
				changesInMyWork = true;
			}
		}
	}


	private void updateAllNeighbours() {
		for (int i = firstCellX; i < firstCellX + height; i++) {
			for (int j = firstCellY; j < firstCellY + width; j++) {
				updateNeighbours(i, j);
			}
		}
	}


	/**
	 * writeMyChange
	 * 
	 * @param x
	 *            cell x index
	 * @param y
	 *            cell y index
	 * @param neighbourIndex
	 *            the neighbor that advanced
	 * @param isAlive
	 *            the new status
	 */

	private void writeMyChange(int x, int y, int neighbourIndex, boolean isAlive) {
		matrix[x][y].setNeighbour(neighbourIndex, isAlive); // this action is
		// synchronized
		synchronized (lock) {
			this.changesInOthers = new Boolean(true);
			this.lock.doNotify();
		}
	}

	/**
	 * gets a row index in the field and returns the row index of the thread
	 * this cell belongs to
	 * 
	 * @param x
	 * @return the row index of the thread this cell belongs to
	 */
	private int calcRow(int x) {
		int row = xIndex;

		if (x >= firstCellX + height)
			row++;

		if (x < firstCellX)
			row--;

		return row;
	}

	/**
	 * gets a column index in the field and returns the column index of the
	 * thread this cell belongs to
	 * 
	 * @param y
	 * @return column index of the thread this cell belongs to
	 */
	private int calcCollumn(int y) {
		int collumn = yIndex;

		if (y >= firstCellY + width)
			collumn++;

		if (y < firstCellY)
			collumn--;

		return collumn;
	}

	/**
	 * the method informs neighbor cells that cell(x,y) had advanced and its new
	 * status is 'isAlive' special treatment for cells belonging to other
	 * workers
	 * 
	 * @param x
	 *            cell x index
	 * @param y
	 *            cell y index
	 * @param isAlive
	 *            the new status
	 */
	private void updateNeighbours(int x, int y) {
		boolean isAlive = matrix[x][y].isAlive();
		boolean[] neighbours = matrix[x][y].neighborsInOtherWorkers();

		int placeofNeighbour = -1;
		for (int i = x - 1; i <= x + 1; i++) {

			// put "dead" cells where there are no neighbors (out of field
			// bounds)
			if (i < 0) { // up
				matrix[x][y].setNeighbour(0, false);
				matrix[x][y].setNeighbour(1, false);
				matrix[x][y].setNeighbour(2, false);
				placeofNeighbour += 3;
				continue;
			}
			if (i >= matrix.length) { // down
				matrix[x][y].setNeighbour(5, false);
				matrix[x][y].setNeighbour(6, false);
				matrix[x][y].setNeighbour(7, false);
				continue;
			}

			for (int j = y - 1; j <= y + 1; j++) {
				placeofNeighbour++;

				if (i == x && j == y) { // current cell, it's me, not a
					// neighbor!!
					placeofNeighbour--;
					continue;
				}

				if (j < 0) { // left
					matrix[x][y].setNeighbour(0, false);
					matrix[x][y].setNeighbour(3, false);
					matrix[x][y].setNeighbour(5, false);
					continue;
				}
				if (j >= matrix[0].length) { // right
					matrix[x][y].setNeighbour(2, false);
					matrix[x][y].setNeighbour(4, false);
					matrix[x][y].setNeighbour(7, false);
					continue;
				}
				
				updateWorkersMatrix(neighbours[placeofNeighbour],placeofNeighbour,isAlive, i,j);

			}
		}
	}


	private void updateWorkersMatrix(boolean belongs, int placeofNeighbour,boolean isAlive, int i, int j) {
		if (belongs) { // the cell belongs to other
			// worker
			int row = calcRow(i);
			int col = calcCollumn(j);
			workersMatrix[row][col].writeMyChange(i, j, Cell
					.indexTranslation(placeofNeighbour), isAlive);
		} else{
			// cells in the same worker
			matrix[i][j].setNeighbour(Cell
					.indexTranslation(placeofNeighbour), isAlive);
		}

		
	}

}
